"""
Procedural sound generation for tank battle game
Creates simple sound effects using pygame.sndarray without external files
"""
import pygame
import numpy as np
from typing import Optional


class ProceduralSoundGenerator:
    """Generate simple sound effects programmatically"""
    
    def __init__(self, sample_rate: int = 22050):
        """
        Initialize sound generator
        
        Args:
            sample_rate: Audio sample rate (Hz)
        """
        self.sample_rate = sample_rate
        
        # Initialize pygame mixer if not already done
        if not pygame.mixer.get_init():
            pygame.mixer.init(frequency=sample_rate, size=-16, channels=1, buffer=512)
    
    def _generate_tone(self, frequency: float, duration: float, 
                       volume: float = 0.3) -> pygame.mixer.Sound:
        """Generate a pure tone"""
        num_samples = int(duration * self.sample_rate)
        samples = np.arange(num_samples)
        waveform = np.sin(2.0 * np.pi * frequency * samples / self.sample_rate)
        
        # Apply envelope (fade in/out to avoid clicks)
        envelope = np.ones(num_samples)
        fade_samples = int(0.01 * self.sample_rate)  # 10ms fade
        envelope[:fade_samples] = np.linspace(0, 1, fade_samples)
        envelope[-fade_samples:] = np.linspace(1, 0, fade_samples)
        
        waveform = waveform * envelope * volume
        
        # Convert to 16-bit integer
        waveform = (waveform * 32767).astype(np.int16)
        
        # Create stereo sound (duplicate mono to both channels)
        stereo = np.zeros((num_samples, 2), dtype=np.int16)
        stereo[:, 0] = waveform
        stereo[:, 1] = waveform
        
        return pygame.sndarray.make_sound(stereo)
    
    def _generate_noise(self, duration: float, volume: float = 0.2) -> pygame.mixer.Sound:
        """Generate white noise"""
        num_samples = int(duration * self.sample_rate)
        waveform = np.random.uniform(-1, 1, num_samples) * volume
        
        # Apply envelope
        envelope = np.ones(num_samples)
        fade_samples = int(0.01 * self.sample_rate)
        envelope[:fade_samples] = np.linspace(0, 1, fade_samples)
        envelope[-fade_samples:] = np.linspace(1, 0, fade_samples)
        
        waveform = waveform * envelope
        waveform = (waveform * 32767).astype(np.int16)
        
        stereo = np.zeros((num_samples, 2), dtype=np.int16)
        stereo[:, 0] = waveform
        stereo[:, 1] = waveform
        
        return pygame.sndarray.make_sound(stereo)
    
    def generate_shoot_sound(self) -> pygame.mixer.Sound:
        """Generate tank shooting sound"""
        duration = 0.15
        num_samples = int(duration * self.sample_rate)
        
        # Create a quick descending tone with noise
        t = np.arange(num_samples) / self.sample_rate
        
        # Frequency sweep from 200Hz to 100Hz
        freq_start = 200
        freq_end = 100
        frequency = freq_start - (freq_start - freq_end) * t / duration
        
        # Generate tone
        phase = 2.0 * np.pi * np.cumsum(frequency) / self.sample_rate
        tone = np.sin(phase) * 0.3
        
        # Add noise
        noise = np.random.uniform(-0.1, 0.1, num_samples)
        
        waveform = tone + noise
        
        # Sharp attack, quick decay
        envelope = np.exp(-t * 15)
        waveform = waveform * envelope
        
        waveform = (waveform * 32767).astype(np.int16)
        stereo = np.zeros((num_samples, 2), dtype=np.int16)
        stereo[:, 0] = waveform
        stereo[:, 1] = waveform
        
        return pygame.sndarray.make_sound(stereo)
    
    def generate_explosion_sound(self) -> pygame.mixer.Sound:
        """Generate explosion sound"""
        duration = 0.8
        num_samples = int(duration * self.sample_rate)
        
        t = np.arange(num_samples) / self.sample_rate
        
        # Low rumble + noise burst
        rumble = np.sin(2.0 * np.pi * 50 * t) * 0.2
        rumble += np.sin(2.0 * np.pi * 80 * t) * 0.15
        
        # Noise burst
        noise = np.random.uniform(-0.4, 0.4, num_samples)
        
        waveform = rumble + noise
        
        # Envelope: quick attack, long decay
        envelope = np.exp(-t * 3)
        envelope[:int(0.02 * self.sample_rate)] = np.linspace(0, 1, int(0.02 * self.sample_rate))
        
        waveform = waveform * envelope
        waveform = (waveform * 32767).astype(np.int16)
        
        stereo = np.zeros((num_samples, 2), dtype=np.int16)
        stereo[:, 0] = waveform
        stereo[:, 1] = waveform
        
        return pygame.sndarray.make_sound(stereo)
    
    def generate_hit_sound(self) -> pygame.mixer.Sound:
        """Generate bullet hit wall sound"""
        duration = 0.1
        num_samples = int(duration * self.sample_rate)
        
        # Short noise burst
        waveform = np.random.uniform(-0.3, 0.3, num_samples)
        
        t = np.arange(num_samples) / self.sample_rate
        envelope = np.exp(-t * 30)
        
        waveform = waveform * envelope
        waveform = (waveform * 32767).astype(np.int16)
        
        stereo = np.zeros((num_samples, 2), dtype=np.int16)
        stereo[:, 0] = waveform
        stereo[:, 1] = waveform
        
        return pygame.sndarray.make_sound(stereo)
    
    def generate_enemy_destroyed_sound(self) -> pygame.mixer.Sound:
        """Generate enemy destroyed sound"""
        duration = 0.5
        num_samples = int(duration * self.sample_rate)
        
        t = np.arange(num_samples) / self.sample_rate
        
        # Descending tone
        freq_start = 400
        freq_end = 50
        frequency = freq_start - (freq_start - freq_end) * t / duration
        
        phase = 2.0 * np.pi * np.cumsum(frequency) / self.sample_rate
        waveform = np.sin(phase) * 0.25
        
        # Add some noise
        waveform += np.random.uniform(-0.1, 0.1, num_samples)
        
        # Envelope
        envelope = np.exp(-t * 5)
        waveform = waveform * envelope
        
        waveform = (waveform * 32767).astype(np.int16)
        stereo = np.zeros((num_samples, 2), dtype=np.int16)
        stereo[:, 0] = waveform
        stereo[:, 1] = waveform
        
        return pygame.sndarray.make_sound(stereo)
    
    def generate_game_over_sound(self) -> pygame.mixer.Sound:
        """Generate game over sound"""
        duration = 1.0
        num_samples = int(duration * self.sample_rate)
        
        t = np.arange(num_samples) / self.sample_rate
        
        # Sad descending tones
        freq1 = 440 - 200 * t / duration
        freq2 = 330 - 150 * t / duration
        
        phase1 = 2.0 * np.pi * np.cumsum(freq1) / self.sample_rate
        phase2 = 2.0 * np.pi * np.cumsum(freq2) / self.sample_rate
        
        waveform = np.sin(phase1) * 0.15 + np.sin(phase2) * 0.15
        
        envelope = np.exp(-t * 2)
        waveform = waveform * envelope
        
        waveform = (waveform * 32767).astype(np.int16)
        stereo = np.zeros((num_samples, 2), dtype=np.int16)
        stereo[:, 0] = waveform
        stereo[:, 1] = waveform
        
        return pygame.sndarray.make_sound(stereo)
    
    def generate_level_complete_sound(self) -> pygame.mixer.Sound:
        """Generate level complete sound"""
        duration = 0.6
        num_samples = int(duration * self.sample_rate)
        
        t = np.arange(num_samples) / self.sample_rate
        
        # Happy ascending arpeggio
        notes = [262, 330, 392, 523]  # C-E-G-C major chord
        waveform = np.zeros(num_samples)
        
        for i, freq in enumerate(notes):
            start_idx = int(i * duration * self.sample_rate / len(notes))
            end_idx = int((i + 1.5) * duration * self.sample_rate / len(notes))
            end_idx = min(end_idx, num_samples)
            
            if start_idx < num_samples:
                note_t = t[start_idx:end_idx] - t[start_idx]
                note_samples = len(note_t)
                phase = 2.0 * np.pi * freq * note_t
                note = np.sin(phase) * 0.2
                
                # Envelope for each note
                note_envelope = np.exp(-note_t * 8)
                note = note * note_envelope
                
                waveform[start_idx:end_idx] += note
        
        waveform = (waveform * 32767).astype(np.int16)
        stereo = np.zeros((num_samples, 2), dtype=np.int16)
        stereo[:, 0] = waveform
        stereo[:, 1] = waveform
        
        return pygame.sndarray.make_sound(stereo)
    
    def generate_menu_select_sound(self) -> pygame.mixer.Sound:
        """Generate menu selection sound"""
        return self._generate_tone(440, 0.1, volume=0.2)
    
    def generate_power_up_sound(self) -> pygame.mixer.Sound:
        """Generate power-up sound"""
        duration = 0.4
        num_samples = int(duration * self.sample_rate)
        
        t = np.arange(num_samples) / self.sample_rate
        
        # Ascending tone
        freq_start = 200
        freq_end = 800
        frequency = freq_start + (freq_end - freq_start) * t / duration
        
        phase = 2.0 * np.pi * np.cumsum(frequency) / self.sample_rate
        waveform = np.sin(phase) * 0.25
        
        envelope = 1 - t / duration
        waveform = waveform * envelope
        
        waveform = (waveform * 32767).astype(np.int16)
        stereo = np.zeros((num_samples, 2), dtype=np.int16)
        stereo[:, 0] = waveform
        stereo[:, 1] = waveform
        
        return pygame.sndarray.make_sound(stereo)
    
    def generate_menu_music(self, duration: float = 10.0) -> pygame.mixer.Sound:
        """Generate simple menu background music (loopable)"""
        num_samples = int(duration * self.sample_rate)
        t = np.arange(num_samples) / self.sample_rate
        
        # Simple chord progression: C - Am - F - G
        # Using arpeggios for a retro game feel
        waveform = np.zeros(num_samples)
        
        # Tempo: 120 BPM = 2 beats per second
        beat_duration = 0.5
        
        # Note frequencies (in Hz)
        notes = [
            # C major: C-E-G-C
            [262, 330, 392, 523],
            # A minor: A-C-E-A  
            [220, 262, 330, 440],
            # F major: F-A-C-F
            [175, 220, 262, 349],
            # G major: G-B-D-G
            [196, 247, 294, 392]
        ]
        
        for beat_idx in range(int(duration / beat_duration)):
            chord_idx = (beat_idx // 4) % len(notes)
            note_idx = beat_idx % 4
            freq = notes[chord_idx][note_idx]
            
            start_sample = int(beat_idx * beat_duration * self.sample_rate)
            end_sample = int((beat_idx + 1) * beat_duration * self.sample_rate)
            
            if end_sample > num_samples:
                end_sample = num_samples
            
            note_t = t[start_sample:end_sample] - t[start_sample]
            note = np.sin(2.0 * np.pi * freq * note_t) * 0.1
            
            # Add harmonics for richer sound
            note += np.sin(2.0 * np.pi * freq * 2 * note_t) * 0.05
            
            # Envelope for each note
            note_envelope = np.exp(-note_t * 4)
            note = note * note_envelope
            
            waveform[start_sample:end_sample] += note
        
        waveform = (waveform * 32767).astype(np.int16)
        stereo = np.zeros((num_samples, 2), dtype=np.int16)
        stereo[:, 0] = waveform
        stereo[:, 1] = waveform
        
        return pygame.sndarray.make_sound(stereo)
    
    def generate_game_music(self, duration: float = 10.0) -> pygame.mixer.Sound:
        """Generate action-packed game music (loopable)"""
        num_samples = int(duration * self.sample_rate)
        t = np.arange(num_samples) / self.sample_rate
        
        waveform = np.zeros(num_samples)
        
        # Faster tempo: 140 BPM
        beat_duration = 0.428  # 60/140
        
        # More aggressive chord progression: Em - C - D - B
        notes = [
            [165, 196, 247, 330],  # E minor
            [131, 165, 196, 262],  # C major
            [147, 185, 220, 294],  # D major
            [123, 165, 185, 247]   # B major
        ]
        
        for beat_idx in range(int(duration / beat_duration)):
            chord_idx = (beat_idx // 4) % len(notes)
            note_idx = beat_idx % 4
            freq = notes[chord_idx][note_idx]
            
            start_sample = int(beat_idx * beat_duration * self.sample_rate)
            end_sample = int((beat_idx + 1) * beat_duration * self.sample_rate)
            
            if end_sample > num_samples:
                end_sample = num_samples
            
            note_t = t[start_sample:end_sample] - t[start_sample]
            
            # Square wave for more aggressive sound
            note = np.sign(np.sin(2.0 * np.pi * freq * note_t)) * 0.08
            
            # Add sub-bass
            note += np.sin(2.0 * np.pi * freq * 0.5 * note_t) * 0.06
            
            # Envelope
            note_envelope = np.exp(-note_t * 6)
            note = note * note_envelope
            
            waveform[start_sample:end_sample] += note
        
        waveform = (waveform * 32767).astype(np.int16)
        stereo = np.zeros((num_samples, 2), dtype=np.int16)
        stereo[:, 0] = waveform
        stereo[:, 1] = waveform
        
        return pygame.sndarray.make_sound(stereo)


# Global instance
_procedural_sound_generator: Optional[ProceduralSoundGenerator] = None


def get_procedural_sound_generator() -> ProceduralSoundGenerator:
    """Get singleton procedural sound generator instance"""
    global _procedural_sound_generator
    
    if _procedural_sound_generator is None:
        _procedural_sound_generator = ProceduralSoundGenerator()
    
    return _procedural_sound_generator
